/*
    Copyright 2010 Barnet Wagman

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

        http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
 */

package com.norbl.util;

import java.util.*;
import java.io.*;
import java.lang.reflect.*;

/**
 *
 * @author Barnet Wagman
 */
public class PropertyList extends HashMap<String,Property> {

    public PropertyList() { super(); }

        /**
         *
         * @param name
         * @return the {@link Property} with the specified name
         *  or null if it does not exist.
         */
    public Property getProperty(String name) {
        if ( name == null ) return(null);
        else return( this.get(name) );
    }

    public Object getPropertyValue(String name) {
        Property p = getProperty(name);
        if ( p != null ) return(p.value);
        else return(null);
    }

    public Property deleteProperty(String name) {
        return((Property) this.remove(name));
    }
    
    public void add(String name,Object val) {
        if ( this.containsKey(name) )
            throw new PropertyExistsException("A property named " +
                        name + " already exists.");
        put(name,new Property(name,val));
    }

    public void add(Property p) {
        if ( this.containsKey(p.name) )
            throw new PropertyExistsException("A property named " +
                        p.name + " already exists.");
        put(p.name,p);
    }

    public boolean hasProperty(String name) {
        return( this.containsKey(name) );
    }

    public List<Property> getProperties() {

        List<Property> props = new ArrayList<Property>();

        for ( Iterator<Property> it = this.values().iterator();
              it.hasNext(); ) {
              props.add(it.next());
        }

        Collections.sort(props);

        return(props);
    }

        /** Combines the elements in this {@link PropertyList} into
         *  a string of newline delimited name/value pairs, of the form
         *  blockquote>
         *  <tt>&lt;name&gt;=&lt;value&gt;</tt><p>
         *  E.g.<br><tt>
         *  AWSAccessKeyID=your-access-key<br>
         *  SecretAccessKey=your-secret-access-key</tt>
         *  </blockquote>
         *
         *  If this object contains no elements, an empty string
         *  (not a <tt>null</tt> is returned.
         * @return property name,value pairs as a string.
         */
    public String toTextForm() {

        if ( this.size() < 1 ) return("");

        List<Property> props = getProperties();
       
        StringBuilder b = new StringBuilder();
        for ( Property p : props ) {
            b.append(p.toPairString() + "\n");
        }
        return(b.toString());
    }

    public String toOneLineString() {

        if ( this.size() < 1 ) return("");

        List<Property> props = getProperties();

        StringBuilder b = new StringBuilder();
        for ( Property p : props ) {
            b.append(p.name + "=" + p.valueToString() + " ");
        }
        return(b.toString().trim());
    }

    public String toString() {

        if ( this.size() < 1 ) return("");

        List<Property> props = getProperties();

        StringBuilder b = new StringBuilder();
        for ( Property p : props ) {
            b.append(p.name + "=" + p.valueToString() + "\n");
        }
        return(b.toString());
    }

        /** Creates a new <tt>PropertyList</tt> that contains
         *  all the properties in <tt>this</tt> and <tt>other</tt>.
         * @param other
         * @return a new <tt>PropertyList</tt> containing this objects
         *  properties with <tt>other</tt>'s properties appended.
         * @throws PropertyExistsException if
         *      <tt>this</tt> and <tt>other</tt> have any
         *      properties on common.
         */
    public PropertyList append(PropertyList other)
        throws PropertyExistsException {

        PropertyList pl = new PropertyList();

        for ( Property p : this.getProperties() ) {
            pl.add(p);
        }

        for ( Property p : other.getProperties() ) {
            pl.add(p);
        }

        return(pl);
    }

    public static PropertyList parseTextForm(String s) {

        PropertyList pl = new PropertyList();

        if ( s == null ) return(pl);

        String[] els = s.split("\n");
        if ( els.length < 1 ) return(pl);

        NEXT: for ( String el : els ) {
            if ( isComment(el) ) continue NEXT;
            Property p = Property.parsePair(el);
            if ( p != null ) pl.add(p);

        }
        return(pl);

    }

        /** Writes a list of <tt>PropertyLists</tt>s to a csv file.
         *  This method assumes that the <tt>PropertyLists</tt>s all
         *  have the same properties; if this is not true, the header
         *  will be wrong and rows may not match.
         *
         *  @param csv the output file; <i><b>Warning</i></b> if this
         *  files already exists and is not a directory, <i><b>it will
         *  be overwritten</i></b>.
         *
         *  @param pls of <tt>PropertyLists</tt>s; they should all have the
         *  same properties.
         *
         *  @throws RuntimeException if <tt>csv</tt> is an extant directory.
         */
    public static void propertyListsToCSVFile(File csv,List<PropertyList> pls) {
        try {
            if ( csv.exists() ) {
                if ( csv.isDirectory() ) throw new RuntimeException(
                                            csv.getPath() + " is a directory.");
                else csv.delete();
            }

            BufferedWriter b = new BufferedWriter(new FileWriter(csv));
            PropertyList pl0 = pls.get(0);
            b.write(propertyNamesToCSV(pl0) + "\n");
            for ( int i = 0; i < pls.size(); i++  ) {
                b.write(propertyValuesToCSV(pls.get(i)) + "\n");
            }
            b.flush();
            b.close();
            System.out.println("Wrote " + pls.size() + " PropertyLists to " +
                                csv.getPath());
        }
        catch(IOException iox) { throw new RuntimeException(iox); }
    }

    public static String propertyNamesToCSV(PropertyList pl) {

        List<Property> prs = pl.getProperties();
        StringBuilder b = new StringBuilder();

        for ( int i = 0; i < prs.size(); i++ ) {
            Property pr = prs.get(i);
            b.append(pr.name);
            if ( i < (prs.size()-1) ) b.append(",");
        }

        return(b.toString());
    }

        /** <tt>Time</tt> values are output as a timestamp.
         *  Embedded commas are replaced with '_'.
         */
    public static String propertyValuesToCSV(PropertyList pl) {

        List<Property> prs = pl.getProperties();
        StringBuilder b = new StringBuilder();

        for ( int i = 0; i < prs.size(); i++ ) {
            Property pr = prs.get(i);
            Object val = pr.value;
            if ( val != null ) {
                if ( !val.getClass().isArray() ) {
                    b.append(val.toString().replace(",","_"));                    
                }
                else b.append(arrayToString(val));
            }
            else b.append("NULL");
            if ( i < (prs.size()-1) ) b.append(",");
        }

        return(b.toString());
    }

    public static String arrayToString(Object array) {

        StringBuilder b = new StringBuilder();

        int n = Array.getLength(array);
        for ( int i = 0; i < n; i++ ) {
            Object el = Array.get(array,i);
            if ( !el.getClass().isArray() )
                b.append(" [" + el.toString().replace(",","_") + "] ");
            else b.append(" [" + arrayToString(el) + "] ");
        }
        return(b.toString());
    }

    private static boolean isComment(String s) {
        if ( s == null ) return(false);
        return( s.trim().startsWith("#") );
    }
}
